﻿using System;
using SFML.Audio;
using SFML.Graphics;
using SFML.Window;

namespace Perlin_Noise
{
    internal class Point
    {
        public int Height { set; get; }

        public Color Color { set; get; }
    }

    internal class Program
    {
        private static void OnClose(object sender, EventArgs e)
        {
            // Close the window when OnClose event is received
            RenderWindow window = (RenderWindow)sender;
            window.Close();
        }

        private static void OnKeyPressed(object sender, KeyEventArgs e)
        {
            RenderWindow window = (RenderWindow)sender;
            if (e.Code == KeyCode.Escape)
                window.Close();

            if (e.Code == KeyCode.Right)
                i += 1;
            if (e.Code == KeyCode.Left)
                i -= 1;

            if (e.Code == KeyCode.R)
                radar = !radar;

            if (e.Code == KeyCode.A)
                advance = !advance;
        }

        static Random rand = new Random(3);

        static int i = 0;
        static bool advance = false;
        static bool radar = false;

        private static float LCGNoise(int X, int Seed)
        {
            float n = X * Seed;

            n = ((int)n << 13) ^ (int)n;

            return (float)(Math.Sin(X) + Math.Sin(1.3f * X) + Math.Cos(3 * X) + Math.Cos(7 * X) + Math.Sin(11 * X) + Math.Cos(87 * X) + Math.Sin(93 * X)) / 7.0f;
            /************************************/
            //return (float)(1.0f - ((n * (n * n * 15731.0f + 789221.0f) + 1376312589.0f) % 2147483647.0f) / 1073741824.0f);
        }

        private static float LinearInterpolate(float A, float B, float X)
        {
            return A * (1.0f - X) + B * X;
        }

        private static float CosineInterpolate(float A, float B, float X)
        {
            float FT = X * (float)Math.PI;
            float F = (1.0f - (float)Math.Cos(FT)) * 0.5f;

            return A * (1.0f - F) + B * F;
        }

        private static float LinInNoise(float X, int Seed)
        {
            float A = LCGNoise((int)(X), Seed);
            float B = LCGNoise((int)(X + 1), Seed);
            X = X - (float)((int)X);

            return LinearInterpolate(A, B, X);
        }

        private static float CosInNoise(float X, int Seed)
        {
            float A = LCGNoise((int)(X), Seed);
            float B = LCGNoise((int)(X + 1), Seed);
            X = X - (float)((int)X);

            return CosineInterpolate(A, B, X);
        }

        private static float PerlinNoise(float X, int Seed, float Persistence)
        {
            return CosInNoise(X, Seed) +
                CosInNoise(2 * X, Seed + 1) * Persistence +
                CosInNoise(4 * X, Seed + 2) * Persistence * Persistence +
                CosInNoise(8 * X, Seed + 3) * Persistence * Persistence * Persistence +
                CosInNoise(16 * X, Seed + 4) * Persistence * Persistence * Persistence * Persistence;/* +
                CosInNoise(32 * X,  Seed + 5) * Persistence * Persistence * Persistence * Persistence * Persistence;*/
        }

        private static float LCGNoise2D(int X, int Y, int Seed)
        {
            float n = (X + 37 * Seed) + (Y + 39 * Seed) * 57;

            n = ((int)n << 13) ^ (int)n;

            //X = X + Y * 57;

            //return (float)(Math.Sin(X) + 0.2 * Math.Sin(12345 * X) +
            //0.3f * Math.Sin(12337 * X) + 0.25f * Math.Cos(13477 * X) +
            //0.6f * Math.Cos(1.95f * X) + 0.7f * Math.Sin(1.3f * X) +
            //Math.Cos(3 * X) + Math.Cos(7 * X) + 0.7f * Math.Sin(11 * X) +
            //Math.Cos(87 * X) + Math.Sin(93 * X)) / 5.0f;

            //return (float)(1.0f - ((n * (n * n * 15731.0f + 789221.0f) + 1376312589.0f) % 2147483647.0f) / 1073741824.0f);
            return (float)(1.0f - ((n * (n * n * 15731543.0f + 789221773.0f) + 13763125897855.0f) % 214748364764.0f) / 107374182432.0f);
            /*************************************/
            //rand = new Random(Seed);
            //for (int i = Y; i > 0; i--)
            //    for (int j = 0; j < 10; j++)
            //        rand.NextDouble();

            //for (int j = X; j > 0; j--)
            //    rand.NextDouble();

            //return (float)(rand.NextDouble() * 2 - 1.0f);
        }

        private static float CosineInterpolate2D(float UL, float UR, float BL, float BR, float X, float Y)
        {
            float A = CosineInterpolate(UL, UR, X);
            float B = CosineInterpolate(BL, BR, X);

            return CosineInterpolate(B, A, Y);
        }

        private static float CosInNoise2D(float X, float Y, int Seed)
        {
            float UL = LCGNoise2D((int)(X), (int)(Y + 1), Seed);
            float UR = LCGNoise2D((int)(X + 1), (int)(Y + 1), Seed);
            float BL = LCGNoise2D((int)(X), (int)(Y), Seed);
            float BR = LCGNoise2D((int)(X + 1), (int)(Y), Seed);

            X = X - (float)((int)X);
            Y = Y - (float)((int)Y);

            return CosineInterpolate2D(UL, UR, BL, BR, X, Y);
        }

        private static float PerlinNoise2D(float X, float Y, int Seed, float Persistence)
        {
            float pN = CosInNoise2D(X, Y, Seed) +
                CosInNoise2D(2 * X, 2 * Y, Seed + 1) * Persistence +
                CosInNoise2D(4 * X, 4 * Y, Seed + 2) * Persistence * Persistence +
                CosInNoise2D(8 * X, 8 * Y, Seed + 3) * Persistence * Persistence * Persistence +
                CosInNoise2D(16 * X, 16 * Y, Seed + 4) * Persistence * Persistence * Persistence * Persistence +
                CosInNoise2D(32 * X, 32 * Y, Seed + 5) * Persistence * Persistence * Persistence * Persistence * Persistence;

            return (float)(pN / (1 + Persistence +
                                    Persistence * Persistence +
                                    Persistence * Persistence * Persistence +
                                    Persistence * Persistence * Persistence * Persistence +
                                    Persistence * Persistence * Persistence * Persistence * Persistence));
        }

        private static void Main(string[] args)
        {
            // Create the main window
            RenderWindow window = new RenderWindow(new VideoMode(1440, 900), "SFML window", Styles.Fullscreen);
            window.Closed += new EventHandler(OnClose);
            window.KeyPressed += new EventHandler<KeyEventArgs>(OnKeyPressed);
            Image a = new Image(720, 450, Color.Black);
            a.Smooth = false;
            Image b = new Image(720, 450, Color.Black);
            b.Smooth = false;
            Image c = new Image(720, 450, Color.Black);
            c.Smooth = false;
            Image d = new Image(720, 450, Color.Black);
            d.Smooth = false;
            Image r = new Image(720, 450, Color.Black);
            r.Smooth = false;
            Image r3D = new Image(1440, 900, Color.Black);
            r3D.Smooth = false;

            Image g = new Image(1440, 900, new Color(255, 255, 255, 20));
            g.Smooth = false;
            for (int j = 0; j < 48; j++)
                for (int u = 0; u < 30; u++)
                {
                    if ((j * u) % 2 != 0)
                        g.SetPixel((uint)(j * 30), (uint)(u * 30), Color.Yellow);
                    else
                        g.SetPixel((uint)(j * 30), (uint)(u * 30), Color.Red);
                }
            int x = 0;

            Sprite spritea = new Sprite(a);
            spritea.Position = new Vector2(0, 0);
            Sprite spriteb = new Sprite(b);
            spriteb.Position = new Vector2(720, 0);
            Sprite spritec = new Sprite(c);
            spritec.Position = new Vector2(0, 450);
            Sprite sprited = new Sprite(d);
            sprited.Position = new Vector2(720, 450);
            Sprite spriter = new Sprite(r);
            spriter.Position = new Vector2(720, 450);
            Sprite render3D = new Sprite(r3D);
            render3D.Position = new Vector2(0, 0);

            Sprite grid = new Sprite(g);
            grid.Position = new Vector2(0, 0);

            Color DarkGreen = new Color(0, 100, 0);

            Point[][] world = new Point[400][];
            for (int j = 0; j < 400; j++)
            {
                world[j] = new Point[400];
                for (int u = 0; u < 400; u++)
                    world[j][u] = new Point();
            }
            // Start the game loop
            while (window.IsOpened())
            {
                //window.Clear();

                //a.CopyScreen(window);

                //for (int u = 0; u < 1440; u++)
                //    for (int j = 0; j < 900; j++)
                //        a.SetPixel((uint)u, (uint)j, Color.Black);

                //if (advance)
                //    i += 5;
                x = i;
                /*
                for (int j = i + 1439; j > i; j--)
                {
                    a.SetPixel((uint)(j - i), (uint)(450 + 200 * PerlinNoise(j / 100.0f, 0, 0.707f)), Color.Green);
                }
                */

                for (int u = 0; u < 450; u++)
                {
                    if (!window.IsOpened())
                        break;
                    if (u % 5 == 0)
                        Console.WriteLine("Loading: " + (u / 5) + "%");
                    else
                        Console.Write("-");
                    for (int j = 0; j < 720; j++)
                    {
                        Color col;

                        //byte color = (byte)(125 + 125 * CosInNoise2D(j / 50.0f, u / 50.0f,  0));
                        //float pN = (5.0f * (1.0f + PerlinNoise2D(j / 500.0f, u / 500.0f, x + 1, 0.807f)));
                        float hCont = (10.0f * (PerlinNoise2D(j / 180.0f, u / 180.0f, x + 2, 0.407f)));
                        float mZones = (10.0f * (PerlinNoise2D(j / 80.0f, u / 80.0f, x + 6, 0.407f)));
                        float mountains = (10.0f * (PerlinNoise2D(j / 50.0f, u / 50.0f, x + 5, 0.6f)));

                        if (mountains < 0) mountains = -mountains;

                        if (mZones < 0) mZones = -mZones;
                        mZones = (float)(-Math.Pow(0.4, mZones) + 1.0f) * 10.0f;
                        mZones = CosineInterpolate(10.0f, 0.0f, mZones / 10.0f);

                        if (hCont < 0) hCont = 0;
                        hCont = (float)(-Math.Pow(0.8, hCont) + 1.0f) * 6.7f;

                        float height = hCont + ((mZones / 10.0f) * mountains * 5.0f / 10.0f + mZones / 60.0f) * hCont / 6.0f + mountains / 10.0f;

                        if (height < 3.5f)
                            col = new Color(0, 80, 150);
                        else if (height < 4)
                            col = new Color(0, 100, 170);
                        else if (height < 4.4f)
                            col = new Color(0, 120, 180);
                        else if (height < 4.6f)
                            col = new Color(240, 215, 150);
                        else if (height > 7.5f)
                            col = new Color((byte)(230 + (9 * (height - 7.5f))), (byte)(230 + (9 * (height - 7.5f))), (byte)(230 + (9 * (height - 7.5f))));
                        else if (height > 5.7f)
                            col = new Color((byte)(120 + (40 * (height - 5.7f))), (byte)(120 + (40 * (height - 5.7f))), (byte)(120 + (40 * (height - 5.7f))));
                        else
                            col = new Color((byte)rand.Next(20), (byte)(180 - (50 * (height - 4.6f))), 0);

                        a.SetPixel((uint)(j), (uint)(u), new Color((byte)(hCont * 25), (byte)(hCont * 25), (byte)(hCont * 25)));
                        b.SetPixel((uint)(j), (uint)(u), new Color((byte)(mZones * 25), (byte)(mZones * 25), (byte)(mZones * 25)));
                        c.SetPixel((uint)(j), (uint)(u), new Color((byte)(mountains * 25), (byte)(mountains * 25), (byte)(mountains * 25)));
                        d.SetPixel((uint)(j), (uint)(u), new Color((byte)(height * 25), (byte)(height * 25), (byte)(height * 25)));
                        r.SetPixel((uint)(j), (uint)(u), col);

                        if (j < 400 && u < 400)
                        {
                            if (height < 4.4f) world[j][u].Color = new Color(200, 185, 150);
                            else world[j][u].Color = col;
                            world[j][u].Height = (int)(10 * height);
                        }
                    }
                    window.Draw(spritea);
                    window.Draw(spriteb);
                    window.Draw(spritec);

                    if (radar)
                        window.Draw(sprited);
                    else
                        window.Draw(spriter);

                    window.Draw(grid);
                    window.Display();
                    window.DispatchEvents();
                }
                advance = false;
                while (!advance && window.IsOpened())
                {
                    window.DispatchEvents();
                    window.Draw(spritea);
                    window.Draw(spriteb);
                    window.Draw(spritec);
                    if (radar)
                        window.Draw(sprited);
                    else
                        window.Draw(spriter);
                    window.Draw(grid);
                    window.Display();
                }

                // **************************************

                window.Clear();
                window.Display();
                window.Clear();
                window.Display();

                Vector2 origen = new Vector2(720, 80);

                for (int iy = 0; iy < 400; iy++)
                {
                    if (!window.IsOpened())
                        break;

                    Color Aqua = new Color(35, 50, 255, 150);

                    for (int ix = 0; ix < 400; ix++)
                    {
                        Shape line1 = Shape.Line(origen + new Vector2(2 * ix - 2 * iy, ix + iy),
                                                origen + new Vector2(2 * ix - 2 * iy, ix + iy - world[ix][iy].Height - 3),
                                                4,
                                                world[ix][iy].Color);
                        window.Draw(line1);

                        if (world[ix][iy].Height < 44)
                        {
                            line1 = Shape.Line(origen + new Vector2(2 * ix - 2 * iy, ix + iy - world[ix][iy].Height - 3),
                                                origen + new Vector2(2 * ix - 2 * iy, ix + iy - 44 - 3),
                                                4,
                                                Aqua);
                            window.Draw(line1);
                        }
                    }
                    window.DispatchEvents();
                    window.Display();
                }

                if (window.IsOpened())
                    window.Capture().SaveToFile("captures\\" + System.DateTime.Now.ToBinary() + ".png");

                window.Display();

                advance = false;
                while (!advance && x == i && window.IsOpened()) { window.DispatchEvents(); }

                // Process events

                //if (x % 2 == 0)
                //    window.Capture().SaveToFile((x - 1) + " - " + x + ".bmp");

                // Update the window
                window.Display();
            }
        }
    }
}